TypeScript
是JavaScript
(ECMAScript)的超集,在支持JavaScript的基础上增强了自己的类型系统,强化了作为弱类型语言JavaScript
在运行前的各种类型检查、依赖检查等等在提高项目代码质量、提升开发效率和降低风险中很有用的编译器特性。
本文基于TypeScript 3.8
类型系统
基本类型
boolean 布尔值
1 | let isDone:boolean = false |
number 数值
1 | let decimal: number = 6; |
string 字符串
1 | let color: string = "blue"; |
Array 数组/列表
两种指定的方式
1
2let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
Tuple 元组
设定特定序列的组合值
1
2
3
4
5
6
7
8
9
10
11// Declare a tuple type
let x: [string, number];
// Initialize it
x = ["hello", 10]; // OK
// Initialize it incorrectly
x = [10, "hello"]; // Error
console.log(x[0].substring(1)); // OK
console.log(x[1].substring(1)); // Error, 'number' does not have 'substring'
Property 'substring' does not exist on type 'number'.
x[3] = "world"; // Error, Property '3' does not exist on type '[string, number]'.
Tuple type '[string, number]' of length '2' has no element at index '3'.1
2
3console.log(x[5].toString()); // Error, Property '5' does not exist on type '[string, number]'.
Object is possibly 'undefined'.
Tuple type '[string, number]' of length '2' has no element at index '5'.
Enum 枚举
给值更友好的名称
默认值从0开始,可以手动设置开始的值,也可以手动设置每一个值
可以通过值来获取值的名称
1
2
3
4
5
6
7enum Color {
Red = 1,
Green,
Blue,
}
let c: Color = Color.Green;
Any 都行
在
JS2TS
的迁移过程中常用any
类型可以赋任意值也能调用任意方法,但object
类型只能赋任意值不能调用任意方法,即使存在也不行1
2
3
4
5
6
7
8
9
10
11
12let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean
let notSure: any = 4;
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)
let prettySure: Object = 4;
prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'.
let list: any[] = [1, true, "free"];
list[1] = 100;
Void 无/空
如果变量定义为
void
,没多少用,只能赋值为null
或者undefined
(在没配置--strickNullChecks
)1
2
3function warnUser(): void {
console.log("This is my warning message");
}1
2let unusable: void = undefined;
unusable = null; // OK if `--strictNullChecks` is not given
Null and Undefined 空对象/未定义
null
和undefined
既是值也是类型null
和undefined
是所有类型的子类型- 可以把
null
和undefined
赋值给其他类型 - 如果开了
--stricNullChecks
,null
和undefined
只能赋值给any
和他们自己(此时undefined
还能赋值给void
),这是为了避免同一个值多个(string
或者null
或者undefined
)类型
1
2
3// Not much else we can assign to these variables!
let u: undefined = undefined;
let n: null = null;- 可以把
Never 绝非
在函数抛异常或者永远不会返回的情况下的返回值类型
never
类型是所有类似的子类型,并且除了它自己,它没有其他子类型1
2
3
4
5
6
7
8
9
10
11
12
13
14// Function returning never must have unreachable end point
function error(message: string): never {
throw new Error(message);
}
// Inferred return type is never
function fail() {
return error("Something failed");
}
// Function returning never must have unreachable end point
function infiniteLoop(): never {
while (true) {}
}
Object 对象
代表所有的非原始类型
object
原始类型目前为
number
,string
,boolean
,symbol
,null
,undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14declare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
create(42); // Error
Argument of type '42' is not assignable to parameter of type 'object | null'.
create("string"); // Error
Argument of type '"string"' is not assignable to parameter of type 'object | null'.
create(false); // Error
Argument of type 'false' is not assignable to parameter of type 'object | null'.
create(undefined); // Error
Argument of type 'undefined' is not assignable to parameter of type 'object | null'.
复合类型
function 函数类型
1 | function add(x: number, y: number): number { |
1 | let myAdd: (x: number, y: number) => number = function( |
- 可选参数(可选参数要在所有必选参数之后)
1 | function buildName(firstName: string, lastName?: string) { |
默认参数(没有位置要求)
1
2
3
4
5
6
7
8function buildName(firstName = "Will", lastName: string) {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // error, too few parameters
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // okay and returns "Bob Adams"
let result4 = buildName(undefined, "Adams"); // okay and returns "Will Adams"剩余参数,在参数最后
1
2
3
4
5function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;明确的this指向
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
// NOTE: The function now explicitly specifies that its callee must be of type Deck
createCardPicker: function(this: Deck) {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
}
};
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
alert("card: " + pickedCard.card + " of " + pickedCard.suit);函数重载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x: { suit: string; card: number }[]): number;
function pickCard(x: number): { suit: string; card: number };
function pickCard(x): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
let myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4 }
];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
class 类类型
- 类有方法和属性,可以继承,ECMAScript 2015或者ES6开始支持
- 类的属性和方法可以有
public
,private
,protected
三种,属性可以用readonly
修饰public
为默认,公开访问private
不能被直接访问,有两种实现方式,ECMAScript用#
在属性名前表示私有,TypeScript用private
关键字protected
可以被派生类访问
static
属性和方法属于类属性或者类方法,不用实例调用- 构造器参数使用属性装饰器,可以声明并初始化对应的属性,
public
,protected
,private
,readonly
- 访问器属性需要把编译器设置为ECMAScript 5 或者更高才支持,访问器属性有
get
没有set
会自动设置为readonly
abstract
类和方法,不能被直接实例化,必须被派生类是实现了才可以- 接口可以继承类,类声明做两件事一个是声明实例的类型,另一个是一个构造器函数
interface 接口类型
基本定义
使用
?
定义可选属性readonly
定义只读属性1
2
3
4
5interface LabeledValue {
label: string;
value?: string
readonly flag: boolean
}1
2
3function printLabel(labeledObj: LabeledValue) {
console.log(labeledObj.label);
}1
2let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);避免过度检测的方式
- 使用类型断言
1 | let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig); |
使用任意类型的额外属性定义
1
2
3
4
5interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
Function 函数类型
1 | interface SearchFunc { |
Indexable 索引类型
- 可以用
readonly
设置只读
1 | interface StringArray { |
Class 类 类型
- 接口描述了类公共的部分,不能描述私有的部分
1 | interface ClockInterface { |
类实现接口,只有类的实例部分会被检测,静态部分不会被检测,可以用类表达式来整合两者
1
2
3
4
5
6
7
8
9
10
11
12
13
14interface ClockConstructor {
new (hour: number, minute: number);
}
interface ClockInterface {
tick();
}
const Clock: ClockConstructor = class Clock implements ClockInterface {
constructor(h: number, m: number) {}
tick() {
console.log("beep beep");
}
};
接口继承
- 接口可以继承,可以多继承
1 | interface Shape { |
混合类型
- 得益于JavaScript动态类型的特性
1 | interface Counter { |
接口继承类
- 继承类的接口具有类的所有属性(不管私有的还是公有的)
- 只有类的后代类能实现这个接口
1 | class Control { |
Literal 字面类型
字符串字面量
- 字面量实现枚举
1 | type Easing = "ease-in" | "ease-out" | "ease-in-out"; |
字面类型实现重载
1
2
3
4
5
6function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLInputElement;
// ... more overloads ...
function createElement(tagName: string): Element {
// ... code goes here ...
}
数字字面量
1 | interface MapConfig { |
Enum 枚举类型
数值枚举
- 默认为0 ,可以指定第一个开始的值,也可以为每一个名称指定值
1 | enum Direction { |
字符串枚举
1 | enum Direction { |
计算和常数值
1 | enum FileAccess { |
Union 和 Intersection 类型
union 联合类型
1 | /** |
返回共用类型的,只能用公共类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19interface Bird {
fly(): void;
layEggs(): void;
}
interface Fish {
swim(): void;
layEggs(): void;
}
declare function getSmallPet(): Fish | Bird;
let pet = getSmallPet();
pet.layEggs();
// Only available in one of the two possible types
pet.swim();
Property 'swim' does not exist on type 'Bird | Fish'.
Property 'swim' does not exist on type 'Bird'.区别对待联合类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24type NetworkLoadingState = {
state: "loading";
};
type NetworkFailedState = {
state: "failed";
code: number;
};
type NetworkSuccessState = {
state: "success";
response: {
title: string;
duration: number;
summary: string;
};
};
// Create a type which represents only one of the above types
// but you aren't sure which it is yet.
type NetworkState =
| NetworkLoadingState
| NetworkFailedState
| NetworkSuccessState;
intersection 交叉类型
1 | interface ErrorHandling { |
交叉类型的应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35class Person {
constructor(public name: string) {}
}
interface Loggable {
log(name: string): void;
}
class ConsoleLogger implements Loggable {
log(name: string) {
console.log(`Hello, I'm ${name}.`);
}
}
// Takes two objects and merges them together
function extend<First extends {}, Second extends {}>(
first: First,
second: Second
): First & Second {
const result: Partial<First & Second> = {};
for (const prop in first) {
if (first.hasOwnProperty(prop)) {
(result as First)[prop] = first[prop];
}
}
for (const prop in second) {
if (second.hasOwnProperty(prop)) {
(result as Second)[prop] = second[prop];
}
}
return result as First & Second;
}
const jim = extend(new Person("Jim"), ConsoleLogger.prototype);
jim.log(jim.name);
generic 通用类型
any 作为通用,不推荐
通用类型变量
1 | function identity<T>(arg: T): T { |
1 | function identity<T>(arg: T): T { |
1 | function identity<T>(arg: T): T { |
1 | interface GenericIdentityFn { |
通用类
1 | class GenericNumber<T> { |
通用约束
1 | interface Lengthwise { |
1 | function getProperty<T, K extends keyof T>(obj: T, key: K) { |
1 | class BeeKeeper { |
类型操作
类型断言
- 在明确值类型的时候可以让解析器有更明确的动作
1 | let someValue: any = "this is a string"; |
类型别名
1 | type Name = string; |
声明合并
- 合并接口内容
- 合并名称空间
- 合并名称空间和类
- 合并名称空间和函数
- 合并名称空间和枚举
代码组织
Module 模块
- 任何包含顶级的
import
或export
的文件被当做模块,反之则当做普通脚本
Export
可以导出变量、函数、类、类型别名、接口
1
2
3
4
5
6
7
8export TYPE OBJECT
export {OBJECT}
export {OBJECT as ANOTHERNAME}
export {OBJECT} from 'MODULE'
export {OBJECT as ANOTHERNAME} from 'MODULE'
export * from 'MODULE'
export * as OBJECT from 'MODULE'
export default DEFAULTexport=
只能用import MODULE = require('MODULE')
来导入1
export = OBJECT
Import
1 | import {OBJECT} from 'MODULE' |
动态模块加载
1 | declare function require(moduleName: string): any; |
模块接口定义
不是
TypeScript
编写的库,需要声明库的接口才能使用库的声明一般定义在
.d.ts
文件里面,类似于C/C++
里面的头文件.h
外围模块
精准声明和引用
声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19declare module "url" {
export interface Url {
protocol?: string;
hostname?: string;
pathname?: string;
}
export function parse(
urlStr: string,
parseQueryString?,
slashesDenoteHost?
): Url;
}
declare module "path" {
export function normalize(p: string): string;
export function join(...paths: any[]): string;
export var sep: string;
}引用
1
2
3/// <reference path="node.d.ts"/>
import * as URL from "url";
let myUrl = URL.parse("http://www.typescriptlang.org");
简化声明和引用
声明
1
declare module "hot-new-module";
引用
1
2import x, { y } from "hot-new-module";
x(y);
模块通配声明 SystemJS 和 AMD
声明
1
2
3
4
5
6
7
8
9declare module "*!text" {
const content: string;
export default content;
}
// Some do it the other way around.
declare module "json!*" {
const value: any;
export default value;
}引用
1
2
3import fileContent from "./xyz.txt!text";
import data from "json!http://example.com/data.json";
console.log(data, fileContent);
UMD模块声明
声明
1
2export function isPrime(x: number): boolean;
export as namespace mathLib;引用
1
2
3import { isPrime } from "math-lib";
isPrime(2);
mathLib.isPrime(2); // ERROR: can't use the global definition from inside a module1
mathLib.isPrime(2);
模块构建指南
导出到接近模块顶级的部分,这样引入就更加轻便
- 导出
namespace
会多了名称空间这一层 - 导出类的静态方法会多了类这一层,可以考虑导出为工具函数
- 导出单个的类或者函数,用
export default
- 导出多个对象,都放在顶层
export xx
- 导出
导入
- 明确导入名称
import {xx,yy} from 'MODULE'
- 使用名称空间方式导入多接口模块
import * as xxx from 'MODULE'
- 明确导入名称
通过导入再重新导出的方式来扩展功能,保持原来的接口
在模块里面不要用名称空间
名称空间是在全局用来对逻辑相关的对象和类型进行分组归类的
在全局用来避免命名冲突很重要
常见的乱用场景
- 单个文件的唯一顶层声明是
export namespace xx{}
, 这种直接删掉并上移一层 - 多个文件有相同的顶层
export namespace xx
,这个不会合并的成一个的
- 单个文件的唯一顶层声明是
Namespace 名称空间
- 用来逻辑上包装一系列的功能
- 可以在多个文件中使用同一个名称空间,也可以用引用标签来给编译器提供引用关系
- 可以通过
import xx = x.y.z
这样的方式来给名称空间设置别名 - 原来使用
script
标签加载的库而不是模块加载器定义的,用namespace
来定义接口
模块和名称空间的使用
- 新项目新代码都用模块来组织代码
- 跨多个文件时使用名称空间
- 最外层没必要使用名称空间,这样会多一层,只有逻辑归类的时候更需要这个
模块
- 模块可以包含代码和声明
- 模块能够明确依赖(比如依赖模块加载器,运行时),能更好的重用代码,强隔离以及更好的开发工具支持
- 官方推荐优先使用模块,在新项目和新代码中
名称空间
- 名称空间是
TypeScript
特有的,ECMAScript
没有 - 名称空间就是代码的逻辑组织,简单好用
- 名称空间可以跨多个文件(使用同一个名就行),也能用
--outFile
拼起来 - 名称空间在Web应用中组织代码的好方式,所有的依赖都包含在
<script>
标签中 - 名称空间有全局污染的问题,难以确认组件依赖,尤其是在大型项目中
模块声明
声明引用
1 | // myModule.d.ts |
1 | // myOtherModule.ts |
模块解析
相对引用
- 相对导入是以
/
,./
,../
开始的 - 除此之外的为非相对导入
MODULE
,@xx/yy
模块解析策略
经典 AMD或者System或者ES2015
- 相对引用相对于引用文件来解析
- 非相对引用从目录树来解析
MODULE.ts
和MODULE.d.ts
- 从当前文件夹找
- 从上一级文件找
- 一直找到根目录
Node
相对引用
- 从相对引用路径的文件来找
- 从相对引用路径的目录来找,目录要是一个包(有
package.json
文件,并有main
字段配置) - 从相对引用路径的目录来找,目录下有文件
index.js
非相对引用,从
node_modules
来解析- 找当前模块同一级的
node_modules
下对应的- 对应的js文件,
MODULE.js
- 对应的模块目录
MODULE
,目录要是一个包(有package.json
文件,并有main
字段配置) - 对应的模块目录,目录下有文件
index.js
- 对应的js文件,
- 找上一级的
node_modules
- 一直找到根目录
- 找当前模块同一级的
TypeScript模块解析
- 相对引用 找
.ts
,.tsx
,.d.ts
- 找
MODULE
- 类似于Node的相对查找方法,
package.json
中找types
属性 - 类似于Node的相对查找方法,找
MODULE/index
- 找
- 非相对引用 找
.ts
,.tsx
,.d.ts
- 找当前目录下
node_modules
下的MODULE
- 类似于Node的相对查找方法,
package.json
中找types
属性 - 找当前目录下
node_modules/@types/MODULE.d.ts
- 类似于Node的相对查找方法,找
MODULE/index
- 找上一级的
node_modules
- 一直找到根目录
- 找当前目录下
最后更新: 2022年03月02日 03:32
原始链接: http://rawbin-.github.io/language/2018-09-25-typescript-ref/